Core Data入门教程

本文是在斯坦福大学公开课 Lecture 12 : Core Data Lecture 13 Final Project Overview课程笔记的基础上,进行了修改和扩充。未接触过Core Data的朋友可以先看下这两节公开课。当然,您也可以直接看本教程,基本上课程中讲到的内容精华下文都会包含。

公开课中讲解的内容,主要是基于UIManagedDocument来管理Core Data。大家可能很难搜索到类似的资料。国内资料还是基于苹果官网做法-使用三大核心对象管理Core Data,因此我在课程笔记中,用红色标题来补充这些内容,请大家阅读时加以区分。在第一部分的末尾,我会给出这两种方式的完整代码。

本文整体内容比较初级,没有涉及Core Data中线程同步、缓存刷新、回滚操作、防止误删等内容,想学习这些内容的请关注我后续文章。

第一部分:斯坦福大学IOS7公开课 Lecture 12 课程笔记

1、创建工程

我们创建一个工程,名称叫CoreDataDemoWithoutTemplate,创建时不要勾选Use Core Data

2、创建.xcdatamodeld文件

创建新文件,在Core Data选项卡中选中Data Model

3、创建实体(Entity)和属性(Attribute)

Add Entity,创建两个实体Photo、Photographer。分别Add Attribute,内容如下:

Photo

Attribute 1 Type
title String
subTitle String
photoURL String
uploadDate Date
thumbnailURL String
thumbnailData BinaryData

Photographer

Attribute 1 Type
name String

4、建立Relationship

Edit Style切换至Graph(两个Style,左侧为Table,右侧为Graph),按着control键拖动任意一个Entity至另一个上。选中Photo,把newRelationship改名为whoTook;选中Photographer,把newRelationship改名为photos

5、改变实体映射关系(一对一改为一对多)

选中Photographer中的photos这个Relationship,在右侧面板中,把Type改为To Many(如下图所示),表示Photographer实体对于Photo实体是一对多的关系。

image description

值得注意的是whoTook与photos也是有类型的:photos由于在此设置为To Many(一对多),因此是NSSet类型;whoTook由于是默认的To One(一对一),因此是指向NSManagedObject的指针。而NSManagedObject则是数据库中所有对象的超类。

6、删除规则

上图中右侧的Delete Rule,一共有四个选项,他们决定了当你删除对象时,实体间的关系会受到怎样的影响:

  • 如果关系的删除规则设定为Nullify(作废)。当A对象的关系指向的B对象被删除后,A对象的关系将被设为nil。对于To Many关系类型,B对象只会从A对象的关系的容器中被移除。
  • 如果关系的删除规则为Cascade(级联),当B对象的关系指向的C对象被删除后,B对象也会被删除。B对象关联(以Cascade删除规则)的二级对象A也会被删除。以此类推。
  • 如果关系的删除规则为Deny(拒绝),如果删除A对象时,A对象的关系指向的B对象仍存在,则删除操作会被拒绝。
  • 如果关系的删除规则为NO Action,当A对象的关系指向的B对象被删除后,A对象保持不变,这意味着A对象的关系会指向一个不存在的对象。如果没有充分的理由,最好不要使用。

7、代码中如何使用

你需要一个NSManagedObjectContext,无论在数据库中创建实体,设置对象属性,还是查询对象都需要这个context。如何获取这个context呢?有两种方式:

  1. 创建UIManagedDocument对象,获取它的managedObjectContext(一个@property)
  2. 创建工程时勾选Use Core Data,这时你的AppDelegate会有一个managedObjectContext的@property(即在AppDelegate中 alloc init 一个NSManagedObjectContext对象),这种方式需要你对context的工作方式有一定了解,需要知道它如何保证线程安全等。而第一种方式UIManagedDocument对象已经帮我们把这些做好了。

UIManagedDocument继承自UIDocument。UIDocument是一系列用于管理存储的机制,而UIManagedDocument把Core Data数据库放入某个特定存储空间,你可以把它看做本地数据库的容器。

补充第7节的内容:使用苹果官网的做法所需的3个主要对象

视频中介绍的方式是通过UIManagedDocument对象来获取NSManagedObjectContext。也提到了“创建工程时勾选Use Core Data”的方式。其实第二种方式才是官网真正推荐的做法。当然第二种方式我们也可以不勾选Use Core Data,然后手动建立。

从官方文档Initializing the Core Data Stack中可以看出,使用第二种方式,需要3个主要对象:

(1)The managed object context (NSManagedObjectContext):

管理数据上下文,你可以将这一部分看作是数据的实际内容,这也是整个数据库中对我们而言最重要的部分,基本上,插入数据,查询数据,删除数据的工作都在这里完成。

管理数据上下文就像便笺簿。当从数据持久层获取数据时,相当于把这些临时的数据拷贝写在便笺簿上,然后就可以随心所欲的修改这些值。通过上下文,可以对数据记录NSManagedObject进行添加删除更改,记录更改后支持撤销和重做。
除非你保存这些数据变化,否则持久层的东西是不会变化。
通常我们将 controller 类或其子类与 NSManagedObjectContext绑定,这样就方便我们动态地生成,获取数据对象等。

NSManagedObjectContext常用的方法:

1
2
3
4
5
6
7
8
9
10
11
-save:将数据对象保存到数据文件
-objectWithID:查询指定 Managed Object ID 的数据对象
-deleteObject:将一个数据对象标记为删除,但是要等到 Context 提交更改时才真正删除数据对象
-undo回滚最后一步操作,这是都 undo/redo 的支持
-lock加锁,常用于多线程以及创建事务。同类接口还有:-unlock and -tryLock
-rollback还原数据文件内容
-reset清除缓存的 Managed Objects。只应当在添加或删除 Persistent Stores 时使用
-undoManager返回当前 Context 所使用的 NSUndoManager
-assignObject: toPersistantStore:由于 Context 可以管理从不同数据文件而来的数据对象,
这个接口的作用就是指定数据对象的存储数据文件(通过指定 PersistantStore 实现)

-executeFetchRequest: error:执行获取数据请求,返回所有匹配的数据对象
(2) The persistent store coordinator (NSPersistentStoreCoordinator):

持久性数据协调器,你可以将这个东西看作是数据库连接器,在这里,你将设置数据存储的名字和位置,以及数据存储的时机。通常从磁盘上的数据文件中读取或存储数据,这些底层的读写就由它来处理。一般我们无需与它直接打交道,上下文已经封装了对它的调用
常用方法:

1
2
3
4
5
6
-addPersistentStoreForURL:configuration:URL:options:error:加载持久化存储数据,对应的卸载接口为 -removePersistentStore:error:
-migratePersistentStore:toURL:options:withType:error:迁移数据存储,效果与 "save as"相似,但是操作成功后,
迁移前的数据存储不可再使用
-managedObjectIDForURIRepresentation:返回给定 URL所指示的数据存储的 object id,如果找不到匹配的数据存储则返回 nil
-persistentStoreForURL:返回指定路径的 Persistent Store
-URLForPersistentStore:返回指定 Persistent Store 的存储路径
(3)The managed object model (NSManagedObjectModel):

被管理的数据模型,用来描述程序的实体、其属性、关系的模型图,你可以将这个东西看作是数据库的轮廓,或者结构。这里包含了各个实体的定义信息,一般来说,你会使用XCode编辑器来操作那个.xcodedatamode文件,添加属性,建立属性之间的关系等等,当然你也可以使用代码。

数据模型包括以下几个部分:

实体(Entity)

对应NSEntityDescription对象,相当于数据库中的一个表。
NSEntityDescription 常用方法:

1
2
3
4
5
+insertNewObjectForEntityForName:inManagedObjectContext: 工厂方法,
根据给定的 Entity 描述,生成相应的 NSManagedObject 对象,并插入 ManagedObjectContext 中。
-managedObjectClassName返回映射到 EntityNSManagedObject 类名
-attributesByName以名字为 key, 返回 Entity 中对应的 Attributes
-relationshipsByName以名字为 key, 返回 Entity 中对应的 Relationships

属性(Property)

对应NSPropertyDescription对象。
Property 为 Entity 的特性,它相当于数据库表中的一列,或者 XML 文件中的 value-key 对中的 key。
它可以描述实体基本属性(Attribute),实体之间的关系(RelationShip),或查询属性(Fetched Property)。

  • 实体的基本属性(Attributes):
    对应NSAttributeDescription对象。存储基本数据,数据类型包括:
    string,date,integer (NSString, NSDate, NSNumber)
  • 实体间的关系(Relationships):
    对应NSRelationshipDescription对象。支持一对一、一对多的关系。
  • 查询属性(Fetched Property):对应NSFetchedPropertyDescription对象。
    根据查询谓词返回指定实体的符合条件的数据对象,表示了一种“弱”的、单项的关系(相当于数据库中的查询语句)。

注意这里是NSManagedObjectModel,要区分一下NSManagedObject:

NSManagedObject是被管理的数据记录,相当于数据库中的一条记录。每一个NSManagedObject对象,都有一个全局ID(类型为:NSManagedObjectID)。每个在NSManagedObjectContext注册过的NSManagedObject,可以通过这个全局 ID 在上下文中查询到。
每个在持久存储层中的对象,都对应一个与上下文相关的NSManagedObject。
常用的方法:

1
2
3
4
-entity 获取实体
-objectID 获取NSManagedObjectID
-valueForKey: 获取指定 Property 的值
-setValue: forKey: 设定指定 Property 的值

8、创建UIManagedDocument对象

1
2
3
4
5
6
7
8
9
//创建UIManagedDocument对象
NSFileManager *fileManager = [NSFileManager defaultManager];
//视频中NSDocumentationDirectory是不对的,应该是NSDocumentDirectory
NSURL *documentURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];

NSString *documentName = @"MyDocument";
NSURL *fileURL = [documentURL URLByAppendingPathComponent:documentName];

UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:fileURL];

9、把UIManagedDocument对象创建在磁盘上并打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//检查文件是否存在
BOOL fileExist = [[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]];

//如果存在,打开它
if (fileExist) {
[document openWithCompletionHandler:^(BOOL success) {
//block to execute when open
}];
}

//如果不存在,创建文件
[document saveToURL:fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
//block to execute when create is done
}];

10、UIDocumentState

UIManagedDocument创建完毕或者打开后,你才可以使用它。修改上面的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//  斯坦福公开课做法-使用UIManagedDocument管理Core Data

#import "ViewController.h"
#import <CoreData/CoreData.h>
#import "Photo.h"
#import "Photographer.h"

@interface ViewController ()

@property(nonatomic,strong)UIManagedDocument *document;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

[self createOrOpenDocumentWithName:@"MyDocument"];
}

#pragma mark - 斯坦福公开课内容
/**
* 创建或者打开数据库
**/

- (void)createOrOpenDocumentWithName:(NSString *)documentName {
//创建UIManagedDocument对象
NSFileManager *fileManager = [NSFileManager defaultManager];

NSURL *documentURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
NSURL *fileURL = [documentURL URLByAppendingPathComponent:documentName];
NSLog(@"fileURL :%@",fileURL);

self.document = [[UIManagedDocument alloc] initWithFileURL:fileURL];
//检查文件是否存在
BOOL fileExist = [[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]];

//如果存在,打开它
if (fileExist) {
NSLog(@"文件存在,将要打开Document");
[self.document openWithCompletionHandler:^(BOOL success) {
//block to execute when open
if (success) {
[self documentIsReady];
}else {
NSLog(@"couldn`t open document at %@",fileURL);
}
}];
}
//如果不存在,创建文件
else {
NSLog(@"文件不存在,将要创建Document");
[self.document saveToURL:fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
//block to execute when create is done
if (success) {
[self documentIsReady];
}else {
NSLog(@"couldn`t create document at %@",fileURL);
}
}];
}
}

- (void)documentIsReady {
if (self.document.documentState == UIDocumentStateNormal) {
//start using document
}
}
@end

UIDocumentState对应的状态有:

  • UIDocumentStateClosed (还没有打开或者创建)
  • UIDocumentStateSavingError (completion handler保存没有成功)
  • UIDocumentStateEditingDisabled (重试)
  • UIDocumentStateInConflict(例如有其他人在使用更新,有冲突到等,常见于使用ICloud时)

执行完之后,查看你的打印输出,Finder中前往MyDocument所在位置,比如我的是:

1
/Users/richfitbi/Library/Developer/CoreSimulator/Devices/512B2C87-F3C5-44D6-8D38-68CA86C3FD26/data/Containers/Data/Application/C55410D2-7D59-4F48-B3A0-E2F0878E7124/

可以看到MyDocument文件结构,并不是像我们想得那样生成sqlite文件,你无法直接查看数据。
image description

11、获取NSManagedObjectContext对象

1
2
3
4
5
6
7
8
9
- (void)documentIsReady {
if (self.document.documentState == UIDocumentStateNormal) {
//start using document

//获得context
NSManagedObjectContext *context = self.document.managedObjectContext;
//操作core data......
}
}

注意:

  • UIManagedDocument是自动保存的。你可以手动保存,代码如下

    1
    2
    3
    [self.document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
    //block to execute when save is done
    }];

保存和创建时的代码几乎无差别,唯一不同的是UIDocumentSaveForOverwriting。该方法需要在主线程调用,且保存过程是异步的。一般我们会让UIManagedDocument自动保存,不会用到该方法。

  • UIManagedDocument是自动关闭的。当没有任何强指针指向它时便会自动关闭。当然你也可以手动关闭,代码如下

    1
    2
    3
    [self.document closeWithCompletionHandler:^(BOOL success) {
    //block to execute when close
    }];

当然该方法也需要在主线程调用,且关闭过程同样是异步的。

12、UIManagedDocument多实例

UIManagedDocument是可以有多实例的(尽管他们操作同一个数据库),每个实例都有自己的context,因此对一个UIManagedDocument实例进行操作不会影响另一个。如果同时对两个UIManagedDocument实例进行写入操作可能会产生冲突,但一般我们只会保留一个实例能够写入,其它只读。那么其中一个实例如何知道另一个实例进行了修改呢?这就需要用到NSNotiFication广播站。

  • 如何注册一个广播?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[center addObserver:self
selector:@selector(contextChanged:)
name:NSManagedObjectContextDidSaveNotification
object:document.managedObjectContext]; // don’t pass nil here!

}
- (void)viewWillDisappear:(BOOL)animated
{
[center removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:document.managedObjectContext];

[super viewWillDisappear:animated];
}
  • 广播得到消息之后能做什么?如何在contextChanged里操作?

(1)可以取回我的所有对象

1
2
3
4
5
6
7
- (void)contextChanged:(NSNotification *)notification
{
// notification.userInfo 返回给我们的是一个字典包含以下的key
NSInsertedObjectsKey //插入的对象数组
NSUpdatedObjectsKey // 有属性更改的对象数组
NSDeletedObjectsKey // 有删除的对象数组
}

(2)Merging changes:使用NSManagedObjectContext的方法,只要把notification传给它,它会自动帮我们把所有的变化合并到我们的context中

1
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification;

补充8、9、10、11、12节内容:初始化Core Data上下文环境

前文补充第7节的内容提到使用苹果官网的做法所需的3个主要对象,按照这种做法,8、9、10、11、12节内容基本用不上了。

官方文档这个步骤名字叫做Initializing the Core Data Stack,直译过来就是“初始化Core Data堆栈”,不太好理解,参考MJ大神的博客,这个步骤叫做“初始化Core Data上下文环境”更加贴切。

我们先设置下XCode的运行方案(Edit Scheme),使XCode可以输出SQL语句。这样我们可以更加清楚的看到Core Data实际做了什么事情。

设置XCode运行时输出SQL语句:

Product -> Scheme —>Edit Scheme,打开的面板选中左侧Run,右侧顶部选中Arguments标签,添加下面的语句

1
-com.apple.CoreData.SQLDebug 1

如图所示:

image description

初始化Core Data上下文环境

先说明一下,以下代码并不是标准做法(通常这些操作会放在AppDelegate中),但基本思路大致相同。详细内容都已添加注释,大家直接看代码好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//  苹果官网做法-使用三打核心对象管理Core Data

#import "ViewController2.h"
#import <CoreData/CoreData.h>
#import "Photo.h"
#import "Photographer.h"

@interface ViewController2 ()

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

@end

@implementation ViewController2

- (void)viewDidLoad {
[super viewDidLoad];
[self initializeCoreData];
}

#pragma mark - 苹果官网做法 Initializing the Core Data Stack

- (void)setManagedObjectContext:(NSManagedObjectContext *)context {
if (!_managedObjectContext) {
_managedObjectContext = context;
}
}


- (void)initializeCoreData
{
//.xcdatamodel文件,编译后为.momd文件
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
NSLog(@"modelURL : %@",modelURL);

//创建NSManagedObjectModel
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSAssert(mom != nil, @"Error initializing Managed Object Model");

//创建NSPersistentStoreCoordinator
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];

//创建NSManagedObjectContext
//initWithConcurrencyType: 初始化,以明确你是使用基于队列的并发模型
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

//NSManagedObjectContext与NSPersistentStoreCoordinator相关联
[moc setPersistentStoreCoordinator:psc];
[self setManagedObjectContext:moc];


NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsURL URLByAppendingPathComponent:@"Model.sqlite"];
NSLog(@"storeURL :%@",storeURL);

//设置NSManagedObjectContext的NSPersistentStoreCoordinator,使之与本地存储文件相关联。
//这样设置之后,就无需管理本地存储,一切交由NSManagedObjectContext来处理。
//无需像视频中讲解的那样,需要检查数据库文件是否存在,需要判断数据库文件是否已打开。
NSError* error;
[self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];
if (error) {
NSLog(@"error: %@", error);
}
}

@end

第一次运行,我们能看到如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
CoreData: annotation: Connecting to sqlite database file at "/Users/richfitbi/Library/Developer/CoreSimulator/Devices/512B2C87-F3C5-44D6-8D38-68CA86C3FD26/data/Containers/Data/Application/9A8ED91D-1C3C-432C-AFB6-4B970B2E555D/Documents/Model.sqlite"
CoreData: annotation: creating schema.
CoreData: sql: pragma page_size=4096
CoreData: sql: pragma auto_vacuum=2
CoreData: sql: BEGIN EXCLUSIVE
CoreData: sql: SELECT TBL_NAME FROM SQLITE_MASTER WHERE TBL_NAME = 'Z_METADATA'
CoreData: sql: CREATE TABLE ZPHOTO ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZWHOTOOK INTEGER, ZUPLOADDATE TIMESTAMP, ZPHOTOURL VARCHAR, ZSUBTITLE VARCHAR, ZTHUMBNAILURL VARCHAR, ZTITLE VARCHAR, ZUNIQUE VARCHAR, ZTHUMBNAILDATA BLOB )
CoreData: sql: CREATE TABLE ZPHOTOGRAPHER ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZNAME VARCHAR )
CoreData: sql: CREATE INDEX IF NOT EXISTS ZPHOTO_ZWHOTOOK_INDEX ON ZPHOTO (ZWHOTOOK)
CoreData: annotation: Creating primary key table.
CoreData: sql: CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER INTEGER, Z_MAX INTEGER)
CoreData: sql: INSERT INTO Z_PRIMARYKEY(Z_ENT, Z_NAME, Z_SUPER, Z_MAX) VALUES(1, 'Photo', 0, 0)
CoreData: sql: INSERT INTO Z_PRIMARYKEY(Z_ENT, Z_NAME, Z_SUPER, Z_MAX) VALUES(2, 'Photographer', 0, 0)
CoreData: sql: CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB)
CoreData: sql: SELECT TBL_NAME FROM SQLITE_MASTER WHERE TBL_NAME = 'Z_METADATA'
CoreData: sql: DELETE FROM Z_METADATA WHERE Z_VERSION = ?
CoreData: sql: INSERT INTO Z_METADATA (Z_VERSION, Z_UUID, Z_PLIST) VALUES (?, ?, ?)
CoreData: sql: SELECT TBL_NAME FROM SQLITE_MASTER WHERE TBL_NAME = 'Z_MODELCACHE'
CoreData: sql: CREATE TABLE Z_MODELCACHE (Z_CONTENT BLOB)
CoreData: sql: INSERT INTO Z_MODELCACHE (Z_CONTENT) VALUES (?)
CoreData: sql: COMMIT
CoreData: sql: pragma journal_mode=wal
CoreData: sql: pragma journal_mode=wal
CoreData: sql: pragma cache_size=200
CoreData: sql: SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA
CoreData: sql: SELECT TBL_NAME FROM SQLITE_MASTER WHERE TBL_NAME = 'Z_MODELCACHE'

第二次运行,我们能看到如下输出:

1
2
3
4
5
6
CoreData: annotation: Connecting to sqlite database file at "/Users/richfitbi/Library/Developer/CoreSimulator/Devices/512B2C87-F3C5-44D6-8D38-68CA86C3FD26/data/Containers/Data/Application/F00EE1CA-3AC0-4673-B1E4-1A16EF776A1D/Documents/Model.sqlite"
CoreData: sql: SELECT TBL_NAME FROM SQLITE_MASTER WHERE TBL_NAME = 'Z_METADATA'
CoreData: sql: pragma journal_mode=wal
CoreData: sql: pragma cache_size=200
CoreData: sql: SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA
CoreData: sql: SELECT TBL_NAME FROM SQLITE_MASTER WHERE TBL_NAME = 'Z_MODELCACHE'

对比两次运行结果,可以看出第一次比第二次多出了许多创建表的逻辑。而你的代码中仅仅创建NSManagedObjectModel、NSPersistentStoreCoordinator、NSManagedObjectContext,并设置了他们之间的关系,无需判断本地数据库文件是否存在,无需判断数据库是否已经打开,这些Core Data都帮你做好了。

执行完之后,查看你的打印输出,Finder中前往sqlite文件位置,可以看到如下结构:

image description

13、插入操作

(1)往数据库中插入对象

1
NSManagedObject *photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:context];
  • 别忘记#import <CoreData/CoreData.h>
  • NSManagedObject实例对应了Core Data Model中的Entity。
  • 新插入的对象所有属性都为nil,除非你在XCode中为属性指定了默认值。

(2)获取和设置属性

1
2
3
- (id)valueForKey:(NSString *)key; 
- (void)setValue:(id)value forKey:(NSString *)key;
//或者也可以用:valueForKeyPath:/setValue:forKeyPath
  • 这里key是你在XCode中创建数据映射时的Attribute name。如”thumbnailURL”、”title”。
  • value则是你已经存储或将要存储到数据库中的值。未存储之前,所有的value都是nil,除非你在XCode中为它指定了默认值。所有value都是object:
    • number和booleans values是 NSNumber 对象
    • Binary data values是 NSData 对象
    • Date values是 NSDate 对象
    • “To-many” mapped relationship是 NSSet 对象(如果指定排序则是NSOrderedSet 对象)
    • “To-one” mapped relationship是 NSManagedObject 对象
  • 对于有关联关系的实体,只用设置一侧,另一侧会自动设置。
1
2
3
4
5
6
7
8
9
NSManagedObject *photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:context];

NSManagedObject *photographer = [NSEntityDescription insertNewObjectForEntityForName:@"Photographer" inManagedObjectContext:context];

[photographer setValue:@"LYL" forKey:@"name"];
[photo setValue:@"My Picture" forKey:@"title"];

//当设置完photo中的relationship之后,photographer的photos 集合会自动添加,无需两边同时设置。
[photo setValue:photographer forKey:@"whoTook"];

(3)保存到磁盘

以上(1)、(2)的内容都是在内存中进行的,当做完这些之后,需要保存到磁盘。前面已经提过,UIManagedDocument是自动保存的,所以此时你什么也不需要做,只是知道有这个过程就可以了。

当UIManagedDocument自动保存后,UIManageredDocumentDidSaveNotification将会广播。

注意在开发过程中,一旦你点击了XCode中的”Stop”,自动保存的过程可能会错过。

14、生成实体的子类

你一定觉得上面的代码很糟糕,setValue:forKey的方式没有类型检查,没有代码提示,一旦后期维护时修改了实体中的属性名,你都无法找到代码中哪里需要修改。所以我们需要创建实体的子类。

选中Model.xcdatamodeld中的任意一个Entities, Editor->Create NSManagedObject Subclass,按照提示完成后续步骤。

注1:实体的子类的命名,官方推荐用MO作为后缀。

image description

注2:如果你使用iOS8之上的SDK,会发现生成的文件和视频中的不一样,多了4个后缀为CoreDataProperties的 Category,请放心,内容还是一样的,只不过实现了代码分离,更多代码放入Category而已。

生成实体的子类后,我们的代码可以这么写,看上去是不是舒服多了?

1
2
3
4
5
6
7
Photo *photo2 = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:context];

Photographer *photographer2 = [NSEntityDescription insertNewObjectForEntityForName:@"Photographer" inManagedObjectContext:context];

photographer2.name = @"YYC";
photo2.title = @"YYC`s picture";
photo2.whoTook = photographer2;

再来看看使用XCode我们生成的这些类。我们把精力放在后缀为 CoreDataProperties 的 Category上面,会发现一些共性:

(1)@dynamic

在.h文件中声明的@Property,在.m文件中用@dynamic来实现。

@dynamic

You use the@dynamic keyword to tell the compiler that you will fulfill the API contract implied by a property either by providing method implementations directly or at runtime using other mechanisms such as dynamic loading of code or dynamic method resolution. It suppresses the warnings that the compiler would otherwise generate if it can”t find suitable implementations. You should only use it if you know that the methods will be available at runtime.

  The example shown in Listing 5-3 illustrates using @dynamic with a subclass of NSManagedObject.

  Listing 5-3 Using @dynamic with NSManagedObject

  @interface MyClass : NSManagedObject

  {

  }

  @property(nonatomic, retain) NSString *value;

  @end

  @implementation MyClass

  @dynamic value;

  @end

  NSManagedObject is provided by the Core Data framework. A managed object class has a corresponding schema that defines attributes and relationships for the class; at runtime, the Core Data framework generates accessor methods for these as necessary. You therefore typically declare properties for the attributes and relationships, but you don”t have to implement the accessor methods yourself, and shouldn”t ask the compiler to do so. If you just declared the property without providing any implementation, however, the compiler would generate a warning. Using @dynamic suppresses the warning.

@dynamic 就是要来告诉编译器,代码中用@dynamic修饰的属性,其getter和setter方法会在程序运行的时候或者用其他方式动态绑定,以便让编译器通过编译。其主要的作用就是用在NSManageObject对象的属性声明上,由于此类对象的属性一般是从Core Data的属性中生成的,Core Data框架会在程序运行的时候为此类属性生成getter和Setter方法。

@dynamic这个关键词,在其它地方通常是用不到的。

它与@synthesize的区别在于:

使用@synthesize,编译器会确实的产生getter和setter方法,而@dynamic仅仅是告诉编译器这两个方法在运行期会有的,无需产生警告。

假设有这么个场景,B类,C类分别继承A类,A类实现某个协议(@protocol),协议中某个属性( somePropety )我不想在A中实现,而在B类,C类中分别实现。如果A中不写任何代码,编译器就会给出警告:

“use @synthesize, @dynamic or provide a method implementation”

这时你给用@dynamic somePropety; 编译器就不会警告,同时也不会产生任何默认代码。

这段引用内容摘自这里

使用@dynamic只是告诉编译器“我在此处无需实现get/set方法,我知道自己要做什么,不用给我警告”。那它到底做了什么呢?其实很简单,我们上面代码也已经用过了:在运行时NSManagedObject会对识别不到的选择器(@property在运行时其实是调用get/set方法),会尝试调用value:forKeysetValue:forKey方法,因此此处无需实现get/set方法。

(2)CoreDataGeneratedAccessors

具有一对多关系的实体,“一”那一方(如本例中的PhotoGrapher),会在.h文件中多出一个CoreDataGeneratedAccessors的Category,并提供了一些针对NSSet集合进行操作的方法。

(3)我们要手动往实体的子类中添加代码,应该使用Category。

这是因为在版本迭代过程中,我们可能经常需要recreate这些实体,我们不希望每次recreate之后,都要重新修改里面的所有方法。所以最好用Category将你自己的代码独立出来。一个经典的例子是:如果我们希望某个实体可以有方法把自己的示例添加到数据库(这在Java Hibernate中很常见吧),最好的做法就是使用Category。

1
2
3
4
5
6
7
8
9
10
11
12
@implementation Photo (Create)

+ (Photo *)photoWithFlickerData:(NSDictionary *)flickerData inManagedObjectContext:(NSManagedObjectContext *)context
{
Photo *photo = ...; // 查看photo是否存在
if (!photo) {
photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:context];
//.....
}
return photo;
}
@end

注意Category不能创建新的实例变量,也不能使用原类中的实例变量,但原类中可以通过@property的方式把自己的实例变量公共话, Category是可以使用原类中的属性的。

补充13、14节内容:使用苹果官网做法往数据库中插入数据

插入操作,和公开课中讲得代码只有一处不同:

公开课中的方式无需手动保存数据到数据库,UIManagedDocument是自动保存的。

苹果官网做法,要保存数据到本地数据库,必须要执行 NSManagedObjectContext 的 save 操作

The creation of NSManagedObject instances does not guarantee their persistence. Once you create an NSManagedObject instance in your managed object context, you must explicitly save that context to persist those changes to your persistent store

1
2
3
4
5
6
7
8
9
10
11
12
{
Photo *photo = ...
Photographer *photographer = ...
photo.title=...
photo.whoTook = photographer;
//...
//此时进行查询,可以查询出数据,但并没有保存到本地数据库文件。
//[self fetchPhotoData];

// 利用上下文对象,将数据同步到本地数据库
[self saveManagedObjectContext];
}

1
2
3
4
5
6
7
8
//利用上下文对象,将数据同步到本地数据库
- (void)saveManagedObjectContext {
NSError *error = nil;
BOOL success = [self.managedObjectContext save:&error];
if (!success) {
[NSException raise:@"同步数据至数据库时发生错误" format:@"%@", [error localizedDescription]];
}
}

15、删除操作

1
[context deleteObject:photo];

注意不是马上删除,而是在之后的某个时刻UIManagedDocument会自动删除。

在这行代码之后,要删除掉所有指向photo的强指针。并且把photo置为nil。

删除操作有一点很酷,当发送deleteObject消息后,Core Data会给所有对象发送prepareForDeletion消息。通常会把这个方法放入Category中。这个方法会在将要删除但还没有删除时发生。

1
2
3
4
5
6
7
@implementation Photo (Deletion)
- (void)prepareForDeletion {
//我们不需要在此处设置whoTool为nil等(会自动执行)
//但假如Photographer有个property叫photocount,表示拍照者拍了多少张照片,可以在此调整。
//比如self.whoTook.photocount--;
}
@end

课程中并没有讲解更新操作,这是因为从代码上看来,更新操作就是更新下某个对象的属性而已:

1
2
3
4
5
6
NSArray *resultList = [context executeFetchRequest:request error:&error];
if (resultList && resultList.count > 0) {
Photo *photo = resultList[0];
//更新操作,在代码上看,只是修改下对象的相关属性。
photo.title = @"YYC`s picture2222";
}

补充15节内容:使用苹果官网做法往数据库中更新、删除数据

和插入操作类似,对于更新、删除炒作,按照苹果官网做法,要保存数据到本地数据库,必须要执行 NSManagedObjectContext 的 save 操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//更新Photo中的数据
- (void)updatePhotoData {
//先找到要更新的内容
NSFetchRequest *request = ...;
NSArray *resultList = ...;
Photo *photo = resultList[0];
//更新操作,在代码上看,只是修改下对象的相关属性。
photo.title = ...;
// 利用上下文对象,将数据同步到本地数据库
[self saveManagedObjectContext];

}

//删除Photo中的数据
- (void)deletePhotoData {
//先找到要删除的内容
NSFetchRequest *request = ...;
NSArray *resultList = ...;
Photo *photo = resultList[0];
//删除
[self.managedObjectContext deleteObject:photo];
// 利用上下文对象,将数据同步到本地数据库
[self saveManagedObjectContext];
}

//saveManagedObjectContext方法见第14节

16、查询操作

第一步:创建NSFetchRequest

(1)指定要取回的实体。

使用NSFetchRequest取回的数据是一个数组,且数组中都是同一种实体的数据,不可能是一部分Photo,一部分Photographer。

(2)指定每批可以取回多少数据,或者最多可以取回多少数据(可选,默认是all)。

(3)使用NSSortDescriptors来对取回的数据数组进行排序。

(4)NSPredicate谓词,限定哪些实体可以被取回(可选,默认是all)。

1
2
3
4
5
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
request.fetchBatchSize = 20; //每次只会取20调数据
request.fetchLimit = 100;//原本有1000条数据,取到100条就结束,不会再往下取
request.sortDescriptors = @[sortDescriptor];
request.predicate = ...;
NSSortDescriptors
1
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES  selector:@selector(localizedStandardCompare:)];

或者

1
2
3
4
NSSortDescriptor *descriptor2 = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES comparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
return NSOrderedSame;
//NSOrderedAscending= -1L, NSOrderedSame, NSOrderedDescending
} ];

第一个参数,表示按什么排序。

第二个参数,ascending 升序。

第三个参数,附加对比规则。比如两个Photo,title相同,它们在数组中如何决定顺序呢,这就用到附加对比规则了。其中localizedStandardCompare:是一个内置选择器,它将根据当前语言环境的语言规则进行排序(语言环境可能会根据大小写,变音符号等等的顺序而发生改变。等同于Finder中的排序规则。

更多内容可以参考这篇文章

NSPredicate
1
2
NSString *serverName = @"flickr-5";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"thumbnailURL containers %@",serverName];

更多内容可以参考这篇文章

高级查询
  • Key Value Coding(键值编码)
    利用键值编码的高级用法,如photos.@count > 5可以进行高级查询,当然还是要结合NSPredicate来完成。类似@count这样的函数,左侧总是dictionary、array、set。更多内容请参考官方文档

  • NSExpression

第二步:执行NSFetchRequest

1
2
3
NSError *error;

NSArray *resultList = [context executeFetchRequest:request error:&error];

一定注意:

  • 发生错误时,resultList为nil
  • 未查询出结果,返回结果是空数组,不是nil

补充16节内容:使用苹果官网做法查询数据

和课程中所讲方式无区别。

17、关于性能

如果你想获取10000条数据,你得不到这真正的10000条数据,你只会得到10000个占位符。只有当你需要展开某条数据时,才真正得到这条数据。比如

1
2
3
for (Photographer *photographer in photographers) {
NSLog(@"fetched photographer %@", photographer);
}

此时你看不到photographer的names,你也许只能看到”unfaulted object”。但下面的情况:

1
2
3
for (Photographer *photographer in photographers) {
NSLog(@"fetched photographer %@", photographer.name);
}

由于在NSLog中需要展开photographer,此时你才能真正的从数据库获取他们。

18、关于线程

此处简要提及,没有细讲,我会在后续文章中补充。

  • NSManagedObjectContext并不是线程安全的。
  • Thread-Safe Access to an NSManagedObjectContext
1
2
3
[context performBlock:^{ //or performBlockAndWait:
//do stuff with context in its safe queue(the queue it was created on)

}];

19、课程提到但没讲的内容

我会在后续文章中补充。

  • Parent Context (advanced)
  • Optimistic locking (deleteConflictsForObject)
  • Rolling back unsaved changes
  • Undo/Redo
  • Staleness (how long after a fetch until a refetch of an object is required? )不刷新时间,也就是取回数据要待多长时间而不被刷新,一定时间后,需要重新取回。

20、完整代码

代码仅仅是为了大家理解,请忽略代码质量 :)

斯坦福公开课做法-使用UIManagedDocument管理Core Data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#import "ViewController.h"
#import <CoreData/CoreData.h>
#import "Photo.h"
#import "Photographer.h"

@interface ViewController ()

@property(nonatomic,strong)UIManagedDocument *document;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

[self createOrOpenDocumentWithName:@"MyDocument"];
}

#pragma mark - 斯坦福公开课内容
/**
* 创建或者打开数据库
**/

- (void)createOrOpenDocumentWithName:(NSString *)documentName {
//创建UIManagedDocument对象
NSFileManager *fileManager = [NSFileManager defaultManager];

NSURL *documentURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
NSURL *fileURL = [documentURL URLByAppendingPathComponent:documentName];
NSLog(@"fileURL :%@",fileURL);

self.document = [[UIManagedDocument alloc] initWithFileURL:fileURL];
//检查文件是否存在
BOOL fileExist = [[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]];

//如果存在,打开它
if (fileExist) {
NSLog(@"文件存在,将要打开Document");
[self.document openWithCompletionHandler:^(BOOL success) {
//block to execute when open
if (success) {
[self documentIsReady];
}else {
NSLog(@"couldn`t open document at %@",fileURL);
}
}];
}
//如果不存在,创建文件
else {
NSLog(@"文件不存在,将要创建Document");
[self.document saveToURL:fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
//block to execute when create is done
if (success) {
[self documentIsReady];
}else {
NSLog(@"couldn`t create document at %@",fileURL);
}
}];
}
}

- (void)documentIsReady {
if (self.document.documentState == UIDocumentStateNormal) {
//start using document
//获得context
NSManagedObjectContext *context = self.document.managedObjectContext;
//操作core data......

//insert=========================================
[self saveDataWithContext1:context];
[self saveDataWithContext2:context];

//query=========================================
[self fetchPhotoDataWithContext:context];

//update=========================================
[self updatePhotoDataWithContext:context];

//delete=========================================
[self deletePhotoDataWithContext:context];

}
}

//使用setValue的原始方式保存数据
- (void)saveDataWithContext1:(NSManagedObjectContext *)context {
NSManagedObject *photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:context];

NSManagedObject *photographer = [NSEntityDescription insertNewObjectForEntityForName:@"Photographer" inManagedObjectContext:context];

[photographer setValue:@"LYL" forKey:@"name"];
[photo setValue:@"LYL`s picture" forKey:@"title"];
[photo setValue:@"flickr-5" forKey:@"thumbnailURL"];
//当设置完photo中的relationship之后,photographer的photos 集合会自动添加,无需两边同时设置。
[photo setValue:photographer forKey:@"whoTook"];
}

//使用NSManagedObject子类的方式保存数据(推荐)
- (void)saveDataWithContext2:(NSManagedObjectContext *)context {
Photo *photo2 = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:context];

Photographer *photographer2 = [NSEntityDescription insertNewObjectForEntityForName:@"Photographer" inManagedObjectContext:context];
photographer2.name = @"YYC";

//只设置Photo的关系
photo2.title = @"YYC`s picture";
photo2.thumbnailURL = @"flickr-5";
photo2.whoTook = photographer2;

//只设置photographer的关系
Photo *photo3 = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:context];
photo3.title = @"YYC`s picture2";
photo3.thumbnailURL = @"flickr-5";
[photographer2 addPhotosObject:photo3];
}



//查询Photo中的数据
- (void)fetchPhotoDataWithContext:(NSManagedObjectContext *)context {

NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES selector:@selector(localizedStandardCompare:)];

NSString *serverName = @"flickr-5";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"thumbnailURL contains %@",serverName];

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
request.fetchBatchSize = 20; //每次只会取20调数据
request.fetchLimit = 100;//原本有1000条数据,取到100条就结束,不会再往下取
request.sortDescriptors = @[sortDescriptor];
request.predicate = predicate;

NSError *error;
NSArray *resultList = [context executeFetchRequest:request error:&error];
for (Photo *photo in resultList) {
NSLog(@"photo : title-%@,thumbnailURL-%@,whoTook-%@",photo.title,photo.thumbnailURL,photo.whoTook.name);
}
}

//更新Photo中的数据
- (void)updatePhotoDataWithContext:(NSManagedObjectContext *)context {
//先找到要更新的内容
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title = %@",@"YYC`s picture2"];

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
request.predicate = predicate;

NSError *error;
NSArray *resultList = [context executeFetchRequest:request error:&error];
if (resultList && resultList.count > 0) {
Photo *photo = resultList[0];
NSLog(@"更新操作-------------------------------");
NSLog(@"will update photo : title-%@,thumbnailURL-%@,whoTook-%@",photo.title,photo.thumbnailURL,photo.whoTook.name);
//更新操作,在代码上看,只是修改下对象的相关属性。
photo.title = @"YYC`s picture2222";
//再次查询,发现数据已经更改。此时要注意,本地数据库中可能并未更改,数据库中的更改是自动完成的。
[self fetchPhotoDataWithContext:context];
}
}

//删除Photo中的数据
- (void)deletePhotoDataWithContext:(NSManagedObjectContext *)context {
//先找到要删除的内容
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title = %@",@"YYC`s picture2222"];

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
request.predicate = predicate;

NSError *error;
NSArray *resultList = [context executeFetchRequest:request error:&error];
if (resultList && resultList.count > 0) {
Photo *photo = resultList[0];
NSLog(@"删除操作-------------------------------");
NSLog(@"will delete photo : title-%@,thumbnailURL-%@,whoTook-%@",photo.title,photo.thumbnailURL,photo.whoTook.name);
//删除
[context deleteObject:photo];

//再次查询,发现数据已经删除。此时要注意,本地数据库中可能并未删除,数据库中的删除是自动完成的。
[self fetchPhotoDataWithContext:context];
}
}

@end

苹果官网做法-使用三大核心对象管理Core Data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//  苹果官网做法-使用三大核心对象管理Core Data

#import "ViewController2.h"
#import <CoreData/CoreData.h>
#import "Photo.h"
#import "Photographer.h"

@interface ViewController2 ()

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

@end

@implementation ViewController2

- (void)viewDidLoad {
[super viewDidLoad];
[self initializeCoreData];

//insert=========================================
[self savePhotoData];

//query==========================================
//savePhotoData即使不执行context的save操作,此处照样可查询出数据
[self fetchPhotoData];

//update=========================================
[self updatePhotoData];

//delete=========================================
[self deletePhotoData];

}

#pragma mark - 苹果官网做法 Initializing the Core Data Stack

- (void)setManagedObjectContext:(NSManagedObjectContext *)context {
if (!_managedObjectContext) {
_managedObjectContext = context;
}
}


- (void)initializeCoreData
{
//.xcdatamodel文件,编译后为.momd文件
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
NSLog(@"modelURL : %@",modelURL);

//创建NSManagedObjectModel
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSAssert(mom != nil, @"Error initializing Managed Object Model");

//创建NSPersistentStoreCoordinator
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];

//创建NSManagedObjectContext
//initWithConcurrencyType: 初始化,以明确你是使用基于队列的并发模型
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

//NSManagedObjectContext与NSPersistentStoreCoordinator相关联
[moc setPersistentStoreCoordinator:psc];
[self setManagedObjectContext:moc];


NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsURL URLByAppendingPathComponent:@"Model.sqlite"];
NSLog(@"storeURL :%@",storeURL);

//设置NSManagedObjectContext的NSPersistentStoreCoordinator,使之与本地存储文件相关联。
//这样设置之后,就无需管理本地存储,一切交由NSManagedObjectContext来处理。
//无需像视频中讲解的那样,需要检查数据库文件是否存在,需要判断数据库文件是否已打开。
NSError* error;
[self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];
if (error) {
NSLog(@"error: %@", error);
}
}

//保存数据操作
- (void)savePhotoData
{
//----------使用setValue的原始方式保存数据-------------//
NSManagedObject *photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:self.managedObjectContext];

NSManagedObject *photographer = [NSEntityDescription insertNewObjectForEntityForName:@"Photographer" inManagedObjectContext:self.managedObjectContext];

[photographer setValue:@"LYL" forKey:@"name"];
[photo setValue:@"LYL`s picture" forKey:@"title"];
[photo setValue:@"flickr-5" forKey:@"thumbnailURL"];
//当设置完photo中的relationship之后,photographer的photos 集合会自动添加,无需两边同时设置。
[photo setValue:photographer forKey:@"whoTook"];

//----------使用NSManagedObject子类的方式保存数据(推荐)-------------//
Photo *photo2 = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:self.managedObjectContext];

Photographer *photographer2 = [NSEntityDescription insertNewObjectForEntityForName:@"Photographer" inManagedObjectContext:self.managedObjectContext];
photographer2.name = @"YYC";

//只设置Photo的关系
photo2.title = @"YYC`s picture";
photo2.thumbnailURL = @"flickr-5";
photo2.whoTook = photographer2;

//只设置photographer的关系
Photo *photo3 = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:self.managedObjectContext];
photo3.title = @"YYC`s picture2";
photo3.thumbnailURL = @"flickr-5";
[photographer2 addPhotosObject:photo3];

//此时进行查询,可以查询出数据,但并没有保存到本地数据库文件。
//[self fetchPhotoData];

// 利用上下文对象,将数据同步到本地数据库
[self saveManagedObjectContext];

}

//利用上下文对象,将数据同步到本地数据库
- (void)saveManagedObjectContext {
NSError *error = nil;
BOOL success = [self.managedObjectContext save:&error];
if (!success) {
[NSException raise:@"同步数据至数据库时发生错误" format:@"%@", [error localizedDescription]];
}
}

//查询Photo中的数据
- (void)fetchPhotoData {

NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES selector:@selector(localizedStandardCompare:)];

NSString *serverName = @"flickr-5";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"thumbnailURL contains %@",serverName];

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
request.fetchBatchSize = 20; //每次只会取20调数据
request.fetchLimit = 100;//原本有1000条数据,取到100条就结束,不会再往下取
request.sortDescriptors = @[sortDescriptor];
request.predicate = predicate;

NSError *error;
NSArray *resultList = [self.managedObjectContext executeFetchRequest:request error:&error];
for (Photo *photo in resultList) {
NSLog(@"photo : title-%@,thumbnailURL-%@,whoTook-%@",photo.title,photo.thumbnailURL,photo.whoTook.name);
}
}

//更新Photo中的数据
- (void)updatePhotoData {
//先找到要更新的内容
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title = %@",@"YYC`s picture2"];

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
request.predicate = predicate;

NSError *error;
NSArray *resultList = [self.managedObjectContext executeFetchRequest:request error:&error];
if (resultList && resultList.count > 0) {
Photo *photo = resultList[0];
NSLog(@"更新操作-------------------------------");
NSLog(@"will update photo : title-%@,thumbnailURL-%@,whoTook-%@",photo.title,photo.thumbnailURL,photo.whoTook.name);
//更新操作,在代码上看,只是修改下对象的相关属性。
photo.title = @"YYC`s picture2222";

// 利用上下文对象,将数据同步到本地数据库
[self saveManagedObjectContext];

//再次查询,发现数据已经更改。
[self fetchPhotoData];
}
}

//删除Photo中的数据
- (void)deletePhotoData {
//先找到要删除的内容
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"title = %@",@"YYC`s picture2222"];

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
request.predicate = predicate;

NSError *error;
NSArray *resultList = [self.managedObjectContext executeFetchRequest:request error:&error];
if (resultList && resultList.count > 0) {
Photo *photo = resultList[0];
NSLog(@"删除操作-------------------------------");
NSLog(@"will delete photo : title-%@,thumbnailURL-%@,whoTook-%@",photo.title,photo.thumbnailURL,photo.whoTook.name);
//删除
[self.managedObjectContext deleteObject:photo];

// 利用上下文对象,将数据同步到本地数据库
[self saveManagedObjectContext];

//再次查询,发现数据已经删除。
[self fetchPhotoData];
}
}

@end

第二部分:斯坦福大学IOS7公开课 Lecture 13 课程笔记

1、Core Data 和 UITableView

NSFetchedResultsController 和UITableView 结合的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (NSUInteger)numberOfSectionsInTableView:(UITableView *)sender
{
return [[self.fetchedResultsController sections] count];
}

- (NSUInteger)tableView:(UITableView *)sender numberOfRowsInSection:(NSUInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)sender cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = ...;

NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
// 或者 Photo *photo = (Photo *) ...

//.....
return cell;
}

NSFetchedResultsController的创建

1
2
3
4
5
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES selector:@selector(localizedStandardCompare:)]];
equest.predicate = [NSPredicate predicateWithFormat:@"thumbnailURL containers %@",@"server"];

NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:@"whoTook.name" cacheName:@"MyPhotoCache"];
  • 参数cacheName,缓存名称,如果该参数传nil则不会缓存。

    注意Core Data中的缓存总是磁盘缓存而不是内存缓存。请确保你使用的那个cacheName代表的缓存,总是关联同一个request且request的内容不会发生改变,否则缓存会失效。

  • 参数sectionNameKeyPath,表示根据返回数据种哪个属性,把数据拆分成不同的section,如果该参数传nil,表示只有一个section。

    注意:使用sectionNameKeyPath时需要确保sortDescriptors必须匹配section keys。换言之,table每行数据的顺序和取回数据的顺序是一致的。所以在sortDescriptors中往往第一个描述器就是按照sectionName进行排序。

NSFetchedResultsController会观察Core Data中数据是否发生改变并自动更新Table

内部是使用key-value observing 机制实现的。当它观察到数据改变,会向它的Delegate发送消息:

1
2
3
4
5
#pragma mark NSFetchedResultsControllerDelegate
//别忘记设置fetchedResultsController.delegate = self;
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath{
//让UITableView更新数据
}

2、斯坦福大学IOS7公开课 Lecture 13 Demo

课程往下就可使讲解一个完整的Demo了,Demo源码可以到这里下载。

我觉得看课程中代码意义不是很大,还是自己写一份比较好理解,下面是我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
//  CoreData与UITableView结合

#import "ViewController3.h"
#import <CoreData/CoreData.h>
#import "Photo.h"
#import "Photographer.h"

#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#define ScreenHeight [UIScreen mainScreen].bounds.size.height
static NSString *tableCellIdentifier = @"Photo";

@interface ViewController3 ()<UITableViewDataSource,NSFetchedResultsControllerDelegate>

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

@property (strong, nonatomic) UITableView *tableView;

@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;

@end

@implementation ViewController3

- (void)viewDidLoad {
[super viewDidLoad];

[self setupTableView];
[self setupButtons];

[self initializeCoreData];

[self fireFetchedResultsController];
}

#pragma mark - Initializing the Core Data Stack

- (void)setManagedObjectContext:(NSManagedObjectContext *)context {
if (!_managedObjectContext) {
_managedObjectContext = context;
}
}


- (void)initializeCoreData
{
//.xcdatamodel文件,编译后为.momd文件
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
NSLog(@"modelURL : %@",modelURL);

//创建NSManagedObjectModel
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSAssert(mom != nil, @"Error initializing Managed Object Model");

//创建NSPersistentStoreCoordinator
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];

//创建NSManagedObjectContext
//initWithConcurrencyType: 初始化,以明确你是使用基于队列的并发模型
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

//NSManagedObjectContext与NSPersistentStoreCoordinator相关联
[moc setPersistentStoreCoordinator:psc];
[self setManagedObjectContext:moc];


NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsURL URLByAppendingPathComponent:@"Model2.sqlite"];
NSLog(@"storeURL :%@",storeURL);

//设置NSManagedObjectContext的NSPersistentStoreCoordinator,使之与本地存储文件相关联。
//这样设置之后,就无需管理本地存储,一切交由NSManagedObjectContext来处理。
//无需像视频中讲解的那样,需要检查数据库文件是否存在,需要判断数据库文件是否已打开。
NSError* error;
[self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];
if (error) {
NSLog(@"error: %@", error);
}
}
#pragma mark - Core Data Methord

/**
* 利用上下文对象,将数据同步到本地数据库
**/

- (void)saveManagedObjectContext {
NSError *error = nil;
BOOL success = [self.managedObjectContext save:&error];
if (!success) {
[NSException raise:@"同步数据至数据库时发生错误" format:@"%@", [error localizedDescription]];
}
}

/**
* 批量插入数据
* 添加5个Photos,每次添加他们都有一个新的Photographer
**/

- (void)addPhotosWithNewPhotographer {
NSUInteger recordTime = [[NSDate date] timeIntervalSince1970]*1000;
NSString *photographerName = [NSString stringWithFormat:@"Photographer-%ld",recordTime];

Photographer *photographer = [NSEntityDescription insertNewObjectForEntityForName:@"Photographer" inManagedObjectContext:self.managedObjectContext];
photographer.name = photographerName;

for (NSInteger i = 0; i < 5; i++) {
//NSUInteger recordTime2 = [[NSDate date] timeIntervalSince1970]*1000*1000;//微秒
Photo *photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:self.managedObjectContext];
photo.title = [NSString stringWithFormat:@"Photo-%ld-%ld",recordTime,i];
photo.whoTook = photographer;
}
// 利用上下文对象,将数据同步到本地数据库
[self saveManagedObjectContext];
}



#pragma mark - UI
- (UITableView *)tableView {
if (!_tableView) {

_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 22, ScreenWidth, ScreenHeight-77)];
}
return _tableView;
}

- (void)setupTableView {
[self.view addSubview:self.tableView];
self.tableView.dataSource = self;
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:tableCellIdentifier];
}

- (void)setupButtons {
UIButton *addButton = [[UIButton alloc] initWithFrame:CGRectMake(ScreenWidth/4, ScreenHeight-40, ScreenWidth/2, 30)];
addButton.backgroundColor = [UIColor orangeColor];
addButton.titleLabel.font = [UIFont systemFontOfSize:10];
[addButton setTitle:@"Add Photos With New Photographer" forState:UIControlStateNormal];
addButton.showsTouchWhenHighlighted = YES;
[addButton addTarget:self action:@selector(addPhotosWithNewPhotographer) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:addButton];

}



- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
[fetchRequest setFetchBatchSize:20];

NSSortDescriptor *sortDescriptor1 = [NSSortDescriptor sortDescriptorWithKey:@"whoTook.name" ascending:YES selector:@selector(localizedStandardCompare:)];
NSSortDescriptor *sortDescriptor2 = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES selector:@selector(localizedStandardCompare:)];
NSArray *sortDescriptors = @[sortDescriptor1,sortDescriptor2];

[fetchRequest setSortDescriptors:sortDescriptors];

_fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:(NSFetchRequest *)fetchRequest
managedObjectContext:(NSManagedObjectContext *)self.managedObjectContext
sectionNameKeyPath:@"whoTook.name"
cacheName:@"MyPhotoCache"];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}

- (void)fireFetchedResultsController {

NSError *error = nil;
if(![self.fetchedResultsController performFetch: &error]){
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
//exit(-1);
abort();
}
}

#pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *sections = [self.fetchedResultsController sections];
id<NSFetchedResultsSectionInfo> sectionInfo = sections[section];
return [sectionInfo numberOfObjects];
//可以简写为:
//return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSArray *sections = [self.fetchedResultsController sections];
id<NSFetchedResultsSectionInfo> sectionInfo = sections[section];
return [sectionInfo name];
//可以简写为:
//return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return [self.fetchedResultsController sectionIndexTitles];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableCellIdentifier forIndexPath:indexPath];
Photo *photo = [self.fetchedResultsController objectAtIndexPath:indexPath];

cell.textLabel.text = photo.title;

return cell;
}

//开启编辑
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}

//编辑的具体逻辑
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
//通过coreData删除对象
Photo *photo = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self.managedObjectContext deleteObject:photo];

[self saveManagedObjectContext];
}
}


#pragma mark NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
NSString *sectionKeyPath = [controller sectionNameKeyPath];
if (sectionKeyPath == nil)
break;
NSManagedObject *changedObject = [controller objectAtIndexPath:indexPath];
NSArray *keyParts = [sectionKeyPath componentsSeparatedByString:@"."];
id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];
for (int i = 0; i < [keyParts count] - 1; i++) {
NSString *onePart = [keyParts objectAtIndex:i];
changedObject = [changedObject valueForKey:onePart];
}
sectionKeyPath = [keyParts lastObject];
NSDictionary *committedValues = [changedObject committedValuesForKeys:nil];
if ([[committedValues valueForKeyPath:sectionKeyPath]isEqual:currentKeyValue])
break;
NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (tableSectionCount != frcSectionCount) {
// Need to insert a section
NSArray *sections = controller.sections;
NSInteger newSectionLocation = -1;
for (id oneSection in sections) {
NSString *sectionName = [oneSection name];
if ([currentKeyValue isEqual:sectionName]) {
newSectionLocation = [sections indexOfObject:oneSection];
break;
}
}
if (newSectionLocation == -1)
return; // uh oh
if (!((newSectionLocation == 0) && (tableSectionCount == 1)
&& ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:newSectionLocation]
withRowAnimation:UITableViewRowAnimationFade];
NSUInteger indices[2] = {newSectionLocation, 0};
newIndexPath = [[NSIndexPath alloc] initWithIndexes:indices length:2];

}
}
case NSFetchedResultsChangeMove:
if (newIndexPath != nil) {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath]
withRowAnimation: UITableViewRowAnimationRight];
}
else {
[self.tableView reloadSections:[NSIndexSet
indexSetWithIndex:[indexPath section]]withRowAnimation:UITableViewRowAnimationFade];
}
break;
default:
break;
}
}

- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)
&& ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)
&& ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
case NSFetchedResultsChangeUpdate:
default:
break;
}
}
@end

参考

Core Data Programming Guide

Core Data入门

在建好的项目中加入CoreData(轉)

Core Data的理解

一个完整的 Core Data 应用

iphone数据存储之-- Core Data的使用(一)

iphone数据存储之-- Core Data的使用(二)

CoreData学习笔记(二)

iOS开发之表视图爱上CoreData